from abc import abstractmethod
from typing import List, Dict, Tuple

import pyqtgraph as pg

from PyQt5 import QtCore, QtGui, QtWidgets
from pts.object import BarData
from PyQt5.QtGui import QFont
from PyQt5.QtCore import QRect, QRectF

from .base import BLACK_COLOR, UP_COLOR, DOWN_COLOR, PEN_WIDTH, BAR_WIDTH
from .manager import BarManager


class ChartItem(pg.GraphicsObject):
    """图表图元基类"""

    def __init__(self, manager: BarManager):
        """
        初始化
        传入的参数是一个K线管理器实例
        """
        super().__init__()

        # K线管理器
        # 接受传入的K线管理器
        self._manager: BarManager = manager

        # 索引图形字典
        self._bar_picutures: Dict[int, QtGui.QPicture] = {}
        self._item_picuture: QtGui.QPicture = None

        # 黑色画刷
        self._black_brush: QtGui.QBrush = pg.mkBrush(color=BLACK_COLOR)

        # 上涨K线画笔和画刷
        self._up_pen: QtGui.QPen = pg.mkPen(
            color=UP_COLOR, width=PEN_WIDTH
        )
        self._up_brush: QtGui.QBrush = pg.mkBrush(color=UP_COLOR)

        # 下跌K线画笔和画刷
        self._down_pen: QtGui.QPen = pg.mkPen(
            color=DOWN_COLOR, width=PEN_WIDTH
        )
        self._down_brush: QtGui.QBrush = pg.mkBrush(color=DOWN_COLOR)

        # 矩形范围
        self._rect_area: Tuple[float, float] = None

        # Very important! Only redraw the visible part and improve speed a lot.
        # 只重画可见的部分，可以极大地提高速度
        self.setFlag(self.ItemUsesExtendedStyleOption)

    @abstractmethod
    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """
        Draw picture for specific bar.
        画一个K线的图形
        """
        pass

    @abstractmethod
    def boundingRect(self) -> QtCore.QRectF:
        """
        Get bounding rectangles for item.
        绑定图元的重画范围
        """
        pass

    @abstractmethod
    def get_y_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
        """
        Get range of y-axis with given x-axis range.

        If min_ix and max_ix not specified, then return range with whole data set.
        给定X轴范围，返回Y轴范围
        """
        pass

    @abstractmethod
    def get_info_text(self, ix: int) -> str:
        """
        Get information text to show by cursor.
        取得用于游标显示的文本
        """
        pass

    def update_history(self, history: List[BarData]) -> BarData:
        """
        Update a list of bar data.
        接受一个K线数据列表
        """
        self._bar_picutures.clear()

        bars = self._manager.get_all_bars()
        for ix, bar in enumerate(bars):
            self._bar_picutures[ix] = None

        self.update()

    def update_bar(self, bar: BarData) -> BarData:
        """
        Update single bar data.
        接受一个单独的K线数据
        """
        ix = self._manager.get_index(bar.datetime)

        self._bar_picutures[ix] = None

        self.update()

    def update(self) -> None:
        """
        Refresh the item.
        重画图元
        """
        if self.scene():
            self.scene().update()

    def paint(
        self,
        painter: QtGui.QPainter,
        opt: QtWidgets.QStyleOptionGraphicsItem,
        w: QtWidgets.QWidget
    ):
        """
        Reimplement the paint method of parent class.

        This function is called by external QGraphicsView.
        重载父类的paint事件
        """
        rect = opt.exposedRect

        min_ix = int(rect.left())
        max_ix = int(rect.right())
        max_ix = min(max_ix, len(self._bar_picutures))

        rect_area = (min_ix, max_ix)
        if rect_area != self._rect_area or not self._item_picuture:
            self._rect_area = rect_area
            self._draw_item_picture(min_ix, max_ix)

        self._item_picuture.play(painter)

    def _draw_item_picture(self, min_ix: int, max_ix: int) -> None:
        """
        Draw the picture of item in specific range.
        在指定范围内画图元的图形
        """
        self._item_picuture = QtGui.QPicture()
        painter = QtGui.QPainter(self._item_picuture)

        for ix in range(min_ix, max_ix):
            bar_picture = self._bar_picutures[ix]

            if bar_picture is None:
                bar = self._manager.get_bar(ix)
                bar_picture = self._draw_bar_picture(ix, bar)
                if bar_picture is None:
                    break

                self._bar_picutures[ix] = bar_picture

            bar_picture.play(painter)

        painter.end()

    def clear_all(self) -> None:
        """
        Clear all data in the item.
        清空图元中的所有数据
        """
        self._item_picuture = None
        self._bar_picutures.clear()
        self.update()

    def _draw_chan(self, ix: int,  painter: QtGui.QPainter):
        """
        显示缠论分析结果
        """
        if ix <= 0:
            return None

        bar = self._manager.get_bar(ix)

        # 画笔
        if (ix < self._manager.nums) and (self._manager.outBi[ix].fenxing != 0):
            # 如果当前是顶点
            # 向前找顶点
            for i in range(ix - 1, 0, -1):
                if self._manager.outBi[i].fenxing != 0:
                    bar2 = self._manager.get_bar(i)
                    if self._manager.outBi[ix].fenxing > 0:
                        # 上涨为红
                        painter.setPen(self._up_pen)
                        painter.drawLine(
                            QtCore.QPointF(i, bar2.low_price),
                            QtCore.QPointF(ix, bar.high_price)
                        )
                    elif self._manager.outBi[ix].fenxing < 0:
                        # 下跌为浅蓝
                        painter.setPen(self._down_pen)
                        painter.drawLine(
                            QtCore.QPointF(i, bar2.high_price),
                            QtCore.QPointF(ix, bar.low_price)
                        )
                    # 结束查找
                    break

            # 如果当前是中枢结束
            if self._manager.outBi[ix].zhsh & 2:
                # 向前找中枢开始
                for i in range(ix - 1, 0, -1):
                    if self._manager.outBi[i].zhsh & 1:
                        painter.setPen(pg.mkPen("m", width=1))
                        painter.drawLine(
                            QtCore.QPointF(i, self._manager.outBi[ix].zhshg),
                            QtCore.QPointF(i, self._manager.outBi[ix].zhshd)
                        )
                        painter.drawLine(
                            QtCore.QPointF(ix, self._manager.outBi[ix].zhshg),
                            QtCore.QPointF(ix, self._manager.outBi[ix].zhshd)
                        )
                        painter.drawLine(
                            QtCore.QPointF(i, self._manager.outBi[ix].zhshg),
                            QtCore.QPointF(ix, self._manager.outBi[ix].zhshg)
                        )
                        painter.drawLine(
                            QtCore.QPointF(i, self._manager.outBi[ix].zhshd),
                            QtCore.QPointF(ix, self._manager.outBi[ix].zhshd)
                        )

                        # 结束查找
                        break

        # Set painter color
        painter.setPen(pg.mkPen("yellow", width=1))

        # 画线
        if (ix < self._manager.nums) and (self._manager.outXian[ix].fenxing != 0):
            # 当前是顶点，向前找顶点
            for i in range(ix - 1, 0, -1):
                if self._manager.outXian[i].fenxing != 0:
                    # 找到了前一个顶点
                    bar2 = self._manager.get_bar(i)
                    if self._manager.outXian[ix].fenxing > 0:
                        painter.drawLine(
                            QtCore.QPointF(i, bar2.low_price),
                            QtCore.QPointF(ix, bar.high_price)
                        )
                    elif self._manager.outXian[ix].fenxing < 0:
                        painter.drawLine(
                            QtCore.QPointF(i, bar2.high_price),
                            QtCore.QPointF(ix, bar.low_price)
                        )
                    # 结束查找
                    break

            # 如果当前是中枢结束
            if self._manager.outXian[ix].zhsh & 2:
                # 向前找中枢开始
                for i in range(ix - 1, 0, -1):
                    if self._manager.outXian[i].zhsh & 1:
                        painter.drawLine(
                            QtCore.QPointF(i, self._manager.outXian[ix].zhshg),
                            QtCore.QPointF(i, self._manager.outXian[ix].zhshd)
                        )
                        painter.drawLine(
                            QtCore.QPointF(ix, self._manager.outXian[ix].zhshg),
                            QtCore.QPointF(ix, self._manager.outXian[ix].zhshd)
                        )
                        painter.drawLine(
                            QtCore.QPointF(i, self._manager.outXian[ix].zhshg),
                            QtCore.QPointF(ix, self._manager.outXian[ix].zhshg)
                        )
                        painter.drawLine(
                            QtCore.QPointF(i, self._manager.outXian[ix].zhshd),
                            QtCore.QPointF(ix, self._manager.outXian[ix].zhshd)
                        )

                        # 结束查找
                        break

        # Set painter color
        painter.setPen(pg.mkPen("white", width=1))

        # 画走势
        if (ix < self._manager.nums) and (self._manager.outXian[ix].fenxing != 0):
            # 如果当前是走势结束
            if self._manager.outXian[ix].xian != 0:
                # 向前找走势开始
                for i in range(ix - 1, 0, -1):
                    if self._manager.outXian[i].xian != 0:
                        # 找到了前一个顶点
                        bar2 = self._manager.get_bar(i)
                        if self._manager.outXian[ix].fenxing > 0:
                            painter.drawLine(
                                QtCore.QPointF(i, bar2.low_price),
                                QtCore.QPointF(ix, bar.high_price)
                            )
                        elif self._manager.outXian[ix].fenxing < 0:
                            painter.drawLine(
                                QtCore.QPointF(i, bar2.high_price),
                                QtCore.QPointF(ix, bar.low_price)
                            )
                        # 结束查找
                        break

        # 画买卖点
        if (ix < self._manager.nums) and (self._manager.outXian[ix].fenxing != 0):
            font = painter.font()
            # font.setFamily("微软雅黑")
            font.setPixelSize(1)
            # font.setPointSizeF(0.5)
            painter.setFont(font)
            # 如果当前是盘一
            if self._manager.outXian[ix].xianmp1 != 0:
                painter.drawText(QtCore.QPointF(ix - 0.5, bar.high_price), "盘一")
                #rectangle: QRectF = QRectF(ix - 0.5, bar.low_price + 0.5, 1, -1)
                #painter.drawPie(rectangle, 30*16, 120*16)

        return None


class CandleItem(ChartItem):
    """K线图元"""

    def __init__(self, manager: BarManager):
        """初始化"""
        super().__init__(manager)

    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """
        画一个K线的图形 - K线体
        """
        # Create objects
        # 创建图形对象
        candle_picture = QtGui.QPicture()
        painter = QtGui.QPainter(candle_picture)

        # Set painter color
        # 根据K线数据，确定画笔的颜色
        if bar.close_price >= bar.open_price:
            # 上涨K线为红框黑体
            painter.setPen(self._up_pen)
            painter.setBrush(self._black_brush)
        else:
            # 下跌K线为浅蓝框浅蓝体
            painter.setPen(self._down_pen)
            painter.setBrush(self._down_brush)

        # Draw candle shadow
        # 画K线影线
        if bar.high_price > bar.low_price:
            painter.drawLine(
                QtCore.QPointF(ix, bar.high_price),
                QtCore.QPointF(ix, bar.low_price)
            )

        # Draw candle body
        # 画K线体
        if bar.open_price == bar.close_price:
            painter.drawLine(
                QtCore.QPointF(ix - BAR_WIDTH, bar.open_price),
                QtCore.QPointF(ix + BAR_WIDTH, bar.open_price),
            )
        else:
            rect = QtCore.QRectF(
                ix - BAR_WIDTH,
                bar.open_price,
                BAR_WIDTH * 2,
                bar.close_price - bar.open_price
            )
            painter.drawRect(rect)

        # 显示缠论分析结果
        self._draw_chan(ix, painter)

        # Finish
        painter.end()
        return candle_picture

    def boundingRect(self) -> QtCore.QRectF:
        """
        绑定图元的重画范围
        """
        min_price, max_price = self._manager.get_price_range()
        rect = QtCore.QRectF(
            0,
            min_price,
            len(self._bar_picutures),
            max_price - min_price
        )
        return rect

    def get_y_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
        """
        Get range of y-axis with given x-axis range.

        If min_ix and max_ix not specified, then return range with whole data set.
        给定X轴范围，返回Y轴范围
        """
        min_price, max_price = self._manager.get_price_range(min_ix, max_ix)
        return min_price, max_price

    def get_info_text(self, ix: int) -> str:
        """
        Get information text to show by cursor.
        取得用于游标显示的文本
        """
        bar = self._manager.get_bar(ix)

        if bar:
            words = [
                "Date",
                bar.datetime.strftime("%Y-%m-%d"),
                "",
                "Time",
                bar.datetime.strftime("%H:%M"),
                "",
                "Open",
                str(bar.open_price),
                "",
                "High",
                str(bar.high_price),
                "",
                "Low",
                str(bar.low_price),
                "",
                "Close",
                str(bar.close_price)
            ]
            text = "\n".join(words)
        else:
            text = ""

        return text


class VolumeItem(ChartItem):
    """"""

    def __init__(self, manager: BarManager):
        """"""
        super().__init__(manager)

    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """
        画一个K线的图形 - 成交量
        """
        # Create objects
        volume_picture = QtGui.QPicture()
        painter = QtGui.QPainter(volume_picture)

        # Set painter color
        if bar.close_price >= bar.open_price:
            painter.setPen(self._up_pen)
            painter.setBrush(self._up_brush)
        else:
            painter.setPen(self._down_pen)
            painter.setBrush(self._down_brush)

        # Draw volume body
        rect = QtCore.QRectF(
            ix - BAR_WIDTH,
            0,
            BAR_WIDTH * 2,
            bar.volume
        )
        painter.drawRect(rect)

        # Finish
        painter.end()
        return volume_picture

    def boundingRect(self) -> QtCore.QRectF:
        """
        绑定图元的重画范围
        """
        min_volume, max_volume = self._manager.get_volume_range()
        rect = QtCore.QRectF(
            0,
            min_volume,
            len(self._bar_picutures),
            max_volume - min_volume
        )
        return rect

    def get_y_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
        """
        Get range of y-axis with given x-axis range.

        If min_ix and max_ix not specified, then return range with whole data set.
        给定X轴范围，返回Y轴范围
        """
        min_volume, max_volume = self._manager.get_volume_range(min_ix, max_ix)
        return min_volume, max_volume

    def get_info_text(self, ix: int) -> str:
        """
        Get information text to show by cursor.
        取得用于游标显示的文本
        """
        bar = self._manager.get_bar(ix)

        if bar:
            text = f"Volume {bar.volume}"
        else:
            text = ""

        return text


class MaItem(ChartItem):
    """Ma图元"""

    def __init__(self, manager: BarManager):
        """初始化"""
        super().__init__(manager)

    def _draw_bar_picture(self, ix: int, bar: BarData) -> QtGui.QPicture:
        """
        画一个Ma线
        """
        if ix <= 0:
            return None

        # Create objects
        # 创建图形对象
        ma_picture = QtGui.QPicture()
        painter = QtGui.QPainter(ma_picture)

        mas = [5, 20]
        mac = [(255, 255, 0), (0, 255, 0)]

        for m in range(len(mas)):
            if ix < mas[m]:
                continue

            # Set painter color
            painter.setPen(pg.mkPen(color=mac[m], width=1))

            sum_ma = 0.0
            for i in range(ix - mas[m], ix):
                bar2 = self._manager.get_bar(i)
                sum_ma = sum_ma + bar2.close_price

            ma1 = sum_ma / mas[m]
            ma0 = (sum_ma - self._manager.get_bar(ix - mas[m]).close_price + bar.close_price) / mas[m]
            painter.drawLine(
                QtCore.QPointF(ix - 1, ma1),
                QtCore.QPointF(ix, ma0)
            )

        # Finish
        painter.end()

        return ma_picture

    def boundingRect(self) -> QtCore.QRectF:
        """
        绑定图元的重画范围
        """
        min_price, max_price = self._manager.get_price_range()
        rect = QtCore.QRectF(
            0,
            min_price,
            len(self._bar_picutures),
            max_price - min_price
        )
        return rect

    def get_y_range(self, min_ix: int = None, max_ix: int = None) -> Tuple[float, float]:
        """
        Get range of y-axis with given x-axis range.

        If min_ix and max_ix not specified, then return range with whole data set.
        给定X轴范围，返回Y轴范围
        """
        min_price, max_price = self._manager.get_price_range(min_ix, max_ix)
        return min_price, max_price

    def get_info_text(self, ix: int) -> str:
        """
        Get information text to show by cursor.
        取得用于游标显示的文本
        """
        text = ""

        return text
